package netTracker.network;

import java.util.*;

/**
 * Keeps a table of nodes in a network by ID, Address pairs. Assigns a unique ID to each node.
 * Modified heavily by David Cummings from the original source by Vijay Garg.
 */
public class NameTable {
    private HashMap<Address, Integer> nodes;	// A map of unique (Address, ID) pairs
    private HashMap<Integer, Address> nodesByID;// A map of unique (ID, Address) pairs
    private static int idCount = 0;				// A count to assign unique IDs
    
    /**
     * Constructs a NameTable with an empty address book.
     */
    public NameTable(){
    	nodes = new HashMap<Address, Integer>();
    	nodesByID = new HashMap<Integer, Address>();
    }
    
    /**
     * Looks up a given address in the address book.
     * @param addr the address to lookup
     * @return the ID associated with the address or ERROR_ID if not found
     */
    public int lookup(Address addr){
    	synchronized(nodes){
    		if (nodes.containsKey(addr))
    			return nodes.get(addr);
    		else
    			return NameServer.ERROR_ID;
    	}
    }
    
    /**
     * Looks up a given id in the address book.
     * @param id the id to lookup
     * @return the address associated with the id or null if not found
     */
    public Address lookup(int id){
    	synchronized(nodes){
    		if (nodesByID.containsKey(id))
    			return nodesByID.get(id);
    		else
    			return null;
    	}
    }
    
    /**
     * Inserts an address into the name table. If it already exists 
     * in the table, replaces the old address and returns the ID 
     * of the address.
     * @param addr the address to add
     * @return the unique ID of the given address
     */
    public int insert(Address addr){
    	synchronized(nodes){
    		int returnID = lookup(addr);
    		if (returnID == NameServer.ERROR_ID){
    			returnID = idCount++;	// Assign unique ID if the entry is new
    			addr.setID(returnID);
    		}
    		nodes.put(addr,returnID);		// Update both address books
    		nodesByID.put(returnID, addr);
    		return returnID;
    	}
    }
    
    public HashMap<Address, Integer> listServers(){
    	synchronized(nodes){
    		// HashMap to store list of servers:
    		HashMap<Address, Integer> servers = new HashMap<Address, Integer>();
    		Collection<Address> c = nodesByID.values();	// Get collection of all addresses
    		Iterator<Address> itr = c.iterator();		// Iterator to iterate through addresses
    		while(itr.hasNext()){
    			Address next = itr.next();				// For each address:
    			if(next.isServer())						// if address is a server,
    				servers.put(next, nodes.get(next));	// add it to the hash map.
    		}
    		return servers;
    	}
    }
    
    public HashMap<Address, Integer> getAddressBook(){
    	return nodes;
    }

    /**
     * Gets the neighbors of the given address as in a ring topology.
     * @param src the address to get the neighbors of
     * @return a map of the two neighbors of the address
     */
	public HashMap<Address, Integer> getNeighbors(Address src) {
		HashMap<Address, Integer> neighbors = new HashMap<Address, Integer>();
		int srcID = src.getID();
		srcID--;
		if (srcID < 0)
			srcID = (nodes.size()-1);
		neighbors.put(lookup(srcID), srcID);
		srcID = (srcID + 2)%nodes.size();
		neighbors.put(lookup(srcID), srcID);
		return neighbors;
	}
}
